package com.bytech.comment.biz.service.comment.Impl;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bytech.comment.biz.mapper.commentThumb.CommentThumbMapper;
import com.bytech.comment.biz.service.emoticon.EmoticonService;
import com.bytech.common.biz.entity.article.Article;
import com.bytech.common.biz.entity.base.BaseResult;
import com.bytech.common.biz.entity.blogUser.BlogUser;
import com.bytech.common.biz.entity.blogUser.BlogUserAgent;
import com.bytech.common.biz.entity.comment.Comment;
import com.bytech.common.biz.entity.comment.CommentDto;
import com.bytech.common.biz.entity.comment.CommentQuery;
import com.bytech.common.biz.entity.commentThumb.CommentThumb;
import com.bytech.common.biz.entity.emoticon.Emoticon;
import com.bytech.common.biz.enums.comment.CommentTypeEnum;
import com.bytech.comment.biz.mapper.comment.CommentMapper;
import com.bytech.comment.biz.service.comment.CommentService;
import com.bytech.common.biz.service.base.BasicServiceImpl;
import com.bytech.common.biz.service.remote.article.RemoteArticleService;
import com.bytech.common.biz.service.remote.user.RemoteBlogUserService;
import com.bytech.common.config.AppGlobalConstants;
import com.bytech.common.utils.ApplicationBeanUtils;
import com.bytech.common.utils.StringUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils;

import static com.bytech.common.config.AppGlobalConstants.COMMENT_SEPARATOR_STR;

/**
 * @Author Zhao
 * @Create 2021-08-04
 */
@Service
public class CommentServiceImpl extends BasicServiceImpl<CommentMapper, Comment> implements CommentService {

    private static final int COMMENT_REPLY_PAGE_SIZE = 3; // 一次加载多少条评论的回复

    @Resource
    private RemoteBlogUserService remoteBlogUserService;
    @Resource
    private CommentMapper commentMapper;
    @Resource
    private CommentThumbMapper commentThumbMapper;
    @Resource
    private RemoteArticleService remoteArticleService;
    @Resource
    private EmoticonService emoticonService;

    @Override
    public int countByType(String type) {
        if (CommentTypeEnum.UNKNOWN.equals(CommentTypeEnum.of(type))) {
            return 0;
        }
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getType, type);
        return count(queryWrapper);
    }

    @Override
    public int countByArticle(String articleId) {
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Comment::getArticleId, articleId);
        return count(queryWrapper);
    }

    @Override
    @Transactional
    public BaseResult<Integer> deleteByArticleId(String articleId) {
        if (StringUtils.isBlank(articleId)) {
            return BaseResult.<Integer>success().data(0).build();
        }
        List<Comment> commentList = commentMapper.selectList(new LambdaQueryWrapper<Comment>().eq(Comment::getArticleId, articleId));
        if (CollectionUtils.isEmpty(commentList)) {
            return BaseResult.<Integer>success().data(0).build();
        }
        commentThumbMapper.delete(new LambdaQueryWrapper<CommentThumb>()
                .in(CommentThumb::getCommentId, commentList.stream().map(Comment::getCommentId).collect(Collectors.toSet())));
        return BaseResult.<Integer>success().data(commentMapper.delete(new LambdaQueryWrapper<Comment>().eq(Comment::getArticleId, articleId))).build();
    }

    @Override
    public List<CommentDto> getLatestComments(String commentType, Integer limit, String userId) {
        if (limit == null || CommentTypeEnum.of(commentType).equals(CommentTypeEnum.UNKNOWN)) {
            return Lists.newArrayList();
        }
        List<Comment> latestCommentList = commentMapper.selectLatest(commentType, limit);
        List<CommentDto> latestCommentDtoList = null;
        if (!CollectionUtils.isEmpty(latestCommentList)) {
            latestCommentDtoList = ApplicationBeanUtils.copyProperties(latestCommentList, CommentDto.class);
            Set<String> commentatorIds = latestCommentDtoList.stream().map(Comment::getCreator).collect(Collectors.toSet());
            List<BlogUser> userList = remoteBlogUserService.getUserByIds(commentatorIds);
            Map<String, BlogUserAgent> userMap = userList.stream().collect(Collectors.toMap(BlogUser::getId, BlogUserAgent::new, (k1, k2) -> k2));
            List<Long> thumbedCommentIds = Lists.newArrayList();
            if (!StringUtils.isBlank(userId)) {
                Set<Long> commentIds = latestCommentDtoList.stream().map(Comment::getId).collect(Collectors.toSet());
                LambdaQueryWrapper<CommentThumb> thumbQuery = new LambdaQueryWrapper<>();
                thumbQuery.in(CommentThumb::getCommentId, commentIds).eq(CommentThumb::getUserId, userId);
                List<CommentThumb> thumbedList = Optional.ofNullable(commentThumbMapper.selectList(thumbQuery)).orElse(Lists.newArrayList());
                thumbedCommentIds = thumbedList.stream().map(CommentThumb::getCommentId).collect(Collectors.toList());
            }
            Set<String> articleIds = latestCommentDtoList.stream().map(CommentDto::getArticleId).collect(Collectors.toSet());
            List<Article> articleList = Optional.ofNullable(remoteArticleService.getArticleByIds(articleIds)).orElse(Lists.newArrayList());
            Map<String, Article> articleMap = articleList.stream().collect(Collectors.toMap(Article::getId, e -> e, (k1, k2) -> k2));
            for (CommentDto commentDto : latestCommentDtoList) {
                commentDto.setCommentator(userMap.get(commentDto.getCreator()));
                commentDto.setThumbed(thumbedCommentIds.contains(commentDto.getId()));
                commentDto.setArticle(articleMap.get(commentDto.getArticleId()));
            }
        }
        populateEmoticon(latestCommentDtoList);
        return latestCommentDtoList;
    }

    @Override
    public BaseResult<List<CommentDto>> pageCommentReply(Long commentId, Integer pageNum, BlogUserAgent userAgent) {
        List<Long> rootIds = Lists.newArrayList();
        rootIds.add(commentId);
        List<Comment> replyList = commentMapper.getCommentRepliesByRootId(rootIds, COMMENT_REPLY_PAGE_SIZE, pageNum);
        List<CommentDto> commentDtoList = ApplicationBeanUtils.copyProperties(replyList, CommentDto.class);
        if (!CollectionUtils.isEmpty(commentDtoList) && userAgent != null && !StringUtils.isBlank(userAgent.getId())) {
            List<Long> replyIds = commentDtoList.stream().map(Comment::getId).collect(Collectors.toList());

            // 找到这些回复的父评论
            List<CommentDto> parentCommentDtoList = Lists.newArrayList();
            Set<Long> parentIds = commentDtoList.stream().map(Comment::getCommentId).collect(Collectors.toSet());
            if (!CollectionUtils.isEmpty(parentIds)) {
                List<Comment> parentCommentList = commentMapper.selectBatchIds(parentIds);
                parentCommentDtoList = ApplicationBeanUtils.copyProperties(parentCommentList, CommentDto.class);
            }

            //获取当前所有评论的评论人信息
            List<CommentDto> allCommentDto = Stream.of(commentDtoList, parentCommentDtoList).
                    flatMap(Collection::stream).collect(Collectors.toList());
            Set<String> commentatorIds = allCommentDto.stream().map(Comment::getCreator).collect(Collectors.toSet());
            Map<String, BlogUserAgent> userMap = Maps.newHashMap();
            List<BlogUser> blogUserList = remoteBlogUserService.getUserByIds(commentatorIds);
            if (!CollectionUtils.isEmpty(blogUserList)) {
                userMap = blogUserList.stream().collect(Collectors.toMap(BlogUser::getId, BlogUserAgent::new, (k1, k2) -> k2));
            }
            // 设置所有父评论的评论人信息
            for (CommentDto parentComment : parentCommentDtoList) {
                parentComment.setCommentator(userMap.get(parentComment.getCreator()));
            }
            Map<Long, CommentDto> parentCommentMap = parentCommentDtoList.stream().collect(Collectors.toMap(CommentDto::getId, e -> e, (k1, k2) -> k2));

            LambdaQueryWrapper<CommentThumb> thumbQueryWrapper = new LambdaQueryWrapper<>();
            thumbQueryWrapper.eq(CommentThumb::getUserId, userAgent.getId()).in(CommentThumb::getCommentId, replyIds);
            List<CommentThumb> thumbList = Optional.ofNullable(commentThumbMapper.selectList(thumbQueryWrapper)).orElse(Lists.newArrayList());
            List<Long> thumbedIds = thumbList.stream().map(CommentThumb::getCommentId).collect(Collectors.toList());
            for (CommentDto curCommentDto : commentDtoList) {
                // 设置回复评论的评论人与父评论 并通过 rootId 分组加入字典备用
                curCommentDto.setCommentator(userMap.get(curCommentDto.getCreator()));
                curCommentDto.setParentComment(parentCommentMap.get(curCommentDto.getCommentId()));
                curCommentDto.setThumbed(thumbedIds.contains(curCommentDto.getId()));
            }
        }
        populateEmoticon(commentDtoList);
        return BaseResult.<List<CommentDto>>success().data(commentDtoList).build();
    }

    @Override
    @Transactional
    public BaseResult<BlogUserAgent> doComment(CommentDto commentDto, BlogUserAgent userAgent, HttpServletRequest request) {
        BaseResult<String> checkResult = checkComment(commentDto, userAgent);
        if (checkResult.isFailure()) {
            return BaseResult.<BlogUserAgent>error(checkResult.getMsg()).build();
        }
        BlogUser user = null;
        if (userAgent == null || StringUtils.isBlank(userAgent.getId())) {
            // 当前登录用户为空那么生成一个用户
            user = new BlogUser();
            user.setName(commentDto.getCommentatorName());
            user.setEmail(commentDto.getCommentatorEmail());
            user.setAvatarUrl(commentDto.getCommentatorAvatar());
            user.setLastLoginIp(request.getRemoteAddr());
            BaseResult<BlogUser> registerResult = remoteBlogUserService.doRegister(user);
            if (registerResult.isFailure()) {
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
            userAgent = new BlogUserAgent(registerResult.getData());
        } else if ((!StringUtils.isBlank(commentDto.getCommentatorName()) && !commentDto.getCommentatorName().equalsIgnoreCase(userAgent.getName()))
                || (!StringUtils.isBlank(commentDto.getCommentatorEmail()) && !commentDto.getCommentatorEmail().equalsIgnoreCase(userAgent.getEmail()))
                || (!StringUtils.isBlank(commentDto.getCommentatorAvatar()) && !commentDto.getCommentatorAvatar().equalsIgnoreCase(userAgent.getAvatarUrl()))) {
            userAgent.setName(commentDto.getCommentatorName());
            userAgent.setEmail(commentDto.getCommentatorEmail());
            userAgent.setAvatarUrl(commentDto.getCommentatorAvatar());
            remoteBlogUserService.doUpdateByAgent(userAgent);
        }
        commentDto.setCreator(userAgent.getId());
        commentDto.setCreateTime(new Date());
        commentDto.setCommentIp(request.getRemoteAddr());
        if (commentDto.getCommentId() != null) {
            Comment parentComment = commentMapper.selectById(commentDto.getCommentId());
            if (parentComment != null) {
                String parentPath = StringUtils.isBlank(parentComment.getParentPath()) ?
                        parentComment.getId().toString() : parentComment.getParentPath() + COMMENT_SEPARATOR_STR + parentComment.getId();
                commentDto.setParentPath(parentPath);
                String rId = parentPath.split(COMMENT_SEPARATOR_STR)[0];
                commentDto.setRootId(Long.valueOf(rId));
            }
        }
        commentMapper.insert(commentDto);
        return BaseResult.<BlogUserAgent>success().data(userAgent).build();
    }

    @Override
    public Page<CommentDto> pageCommentByQuery(CommentQuery<Comment> commentQuery) {
        LambdaQueryWrapper<Comment> rootQueryWrapper = buildCommentQueryWrapper(commentQuery);
        rootQueryWrapper.orderByDesc(Comment::getCreateTime).isNull(Comment::getCommentId);
        page(commentQuery, rootQueryWrapper);
        Page<CommentDto> rootCommentPageResult = commentQuery.translateRecordsTo(CommentDto.class);
        if (rootCommentPageResult != null && !CollectionUtils.isEmpty(rootCommentPageResult.getRecords())) {
            // 通过根评论节点 分组查询每个根节点中按照时间倒序的前 N 条回复
            List<Long> rootCommentIds = rootCommentPageResult.getRecords().stream().map(Comment::getId).collect(Collectors.toList());
            List<Comment> replyList = commentMapper.getCommentRepliesByRootId(rootCommentIds, COMMENT_REPLY_PAGE_SIZE, 1);
            List<CommentDto> replyDtoList = ApplicationBeanUtils.copyProperties(replyList, CommentDto.class);

            // 找到这些回复的父评论
            Set<Long> parentIds = replyDtoList.stream().map(Comment::getCommentId).collect(Collectors.toSet());
            List<CommentDto> parentCommentDtoList = Lists.newArrayList();
            if (!CollectionUtils.isEmpty(parentIds)) {
                List<Comment> parentCommentList = commentMapper.selectBatchIds(parentIds);
                parentCommentDtoList = ApplicationBeanUtils.copyProperties(parentCommentList, CommentDto.class);
            }

            //获取当前所有评论的评论人信息
            List<CommentDto> allCommentDto = Stream.of(replyDtoList, rootCommentPageResult.getRecords(), parentCommentDtoList).
                    flatMap(Collection::stream).collect(Collectors.toList());
            populateEmoticon(allCommentDto);
            List<Long> thumbedIds = Lists.newArrayList();
            if (!StringUtils.isBlank(commentQuery.getMyUserId())) {
                // 获取当前用户对当前所有评论的点赞状态
                Set<Long> commentIds = allCommentDto.stream().map(Comment::getId).collect(Collectors.toSet());
                thumbedIds = commentThumbMapper.selectThumbedIdsByCommentIdsAndUserId(commentIds, commentQuery.getMyUserId());
            }

            Set<String> commentatorIds = allCommentDto.stream().map(Comment::getCreator).collect(Collectors.toSet());
            Map<String, BlogUserAgent> userMap = Maps.newHashMap();
            List<BlogUser> blogUserList = remoteBlogUserService.getUserByIds(commentatorIds);
            if (!CollectionUtils.isEmpty(blogUserList)) {
                userMap = blogUserList.stream().collect(Collectors.toMap(BlogUser::getId, BlogUserAgent::new, (k1, k2) -> k2));
            }
            // 设置所有父评论的评论人信息
            for (CommentDto parentComment : parentCommentDtoList) {
                parentComment.setCommentator(userMap.get(parentComment.getCreator()));
            }
            Map<Long, CommentDto> parentCommentMap = parentCommentDtoList.stream().collect(Collectors.toMap(CommentDto::getId, e -> e, (k1, k2) -> k2));
            Map<Long, List<CommentDto>> repliesMap = Maps.newHashMap();
            if (!CollectionUtils.isEmpty(replyDtoList)) {
                for (CommentDto curCommentDto : replyDtoList) {
                    // 设置回复评论的评论人与父评论 并通过 rootId 分组加入字典备用
                    curCommentDto.setCommentator(userMap.get(curCommentDto.getCreator()));
                    curCommentDto.setParentComment(parentCommentMap.get(curCommentDto.getCommentId()));
                    curCommentDto.setThumbed(thumbedIds.contains(curCommentDto.getId()));
                    List<CommentDto> curCommentList = Optional.ofNullable(repliesMap.get(curCommentDto.getRootId())).orElse(Lists.newArrayList());
                    curCommentList.add(curCommentDto);
                    repliesMap.put(curCommentDto.getRootId(), curCommentList);
                }
            }
            for (CommentDto curDto : rootCommentPageResult.getRecords()) {
                // 设置根评论的评论人和其回复评论列表，列表按照时间倒序
                curDto.setCommentator(userMap.get(curDto.getCreator()));
                curDto.setThumbed(thumbedIds.contains(curDto.getId()));
                List<CommentDto> curReplies = Optional.ofNullable(repliesMap.get(curDto.getId())).orElse(Lists.newArrayList());
                curReplies.sort((o1, o2) -> o2.getCreateTime().compareTo(o1.getCreateTime()));
                curDto.setReplyList(curReplies);
                curDto.setHasMoreReply(curReplies.size() == COMMENT_REPLY_PAGE_SIZE);
            }
        }
        return rootCommentPageResult;
    }

    @Override
    public void populateEmoticon(List<CommentDto> commentDtoList) {
        if (CollectionUtils.isEmpty(commentDtoList)) {
            return;
        }
        List<Long> emoticonIds = Lists.newArrayList();
        for (CommentDto commentDto : commentDtoList) {
            try {
                List<String> emoticonIdTags = StringUtils.getStrsBetween(AppGlobalConstants.EMOTICON_REG_START_STR, AppGlobalConstants.EMOTICON_REG_END_STR, commentDto.getCommentContent());
                emoticonIdTags.forEach(id -> {
                    emoticonIds.add(Long.valueOf(id));
                });
            } catch (NumberFormatException ignored) {
            }
        }
        if (!CollectionUtils.isEmpty(emoticonIds)) {
            List<Emoticon> emoticonList = emoticonService.list(new LambdaQueryWrapper<Emoticon>().in(Emoticon::getId, emoticonIds));
            if (!CollectionUtils.isEmpty(emoticonList)) {
                Map<Long, Emoticon> emoticonMap = emoticonList.stream().collect(Collectors.toMap(Emoticon::getId, e -> e, (k1, k2) -> k2));
                for (CommentDto commentDto : commentDtoList) {
                    try {
                        List<String> emoticonIdTags = StringUtils.getStrsBetween(
                                AppGlobalConstants.EMOTICON_REG_START_STR, AppGlobalConstants.EMOTICON_REG_END_STR, commentDto.getCommentContent());
                        emoticonIdTags.forEach(tag -> {
                            Long id = Long.valueOf(tag);
                            Optional.of(emoticonMap.get(id)).ifPresent(e -> {
                                String url = e.getImgUrl();
                                String emoticonTag = MessageFormat.format(AppGlobalConstants.EMOTICON_TAG_FORMAT, url);
                                commentDto.setCommentContent(commentDto.getCommentContent().replace(
                                        AppGlobalConstants.EMOTICON_REG_START_STR + tag + AppGlobalConstants.EMOTICON_REG_END_STR, emoticonTag));
                            });
                        });
                    } catch (NumberFormatException ignored) {
                    }
                }
            }
        }
    }

    private LambdaQueryWrapper<Comment> buildCommentQueryWrapper(CommentQuery<Comment> commentQuery) {
        LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
        if (commentQuery == null) {
            return queryWrapper;
        }
        if (!StringUtils.isBlank(commentQuery.getArticleId())) {
            queryWrapper.eq(Comment::getArticleId, commentQuery.getArticleId());
        }
        if (!StringUtils.isBlank(commentQuery.getType())) {
            queryWrapper.eq(Comment::getType, commentQuery.getType());
        }
        return queryWrapper;
    }

    private BaseResult<String> checkComment(CommentDto commentDto, BlogUserAgent userAgent) {
        if (commentDto == null) {
            return BaseResult.<String>error("评论内容不能为空").build();
        }
        if (!StringUtils.isBlank(commentDto.getCreator()) && userAgent != null && !commentDto.getCreator().equalsIgnoreCase(userAgent.getId())) {
            // 评论人与当前 token 用户不一致
            return BaseResult.<String>error("用户信息不正确，您可以尝试刷新页面").build();
        }
        if (StringUtils.isBlank(commentDto.getCommentContent())) {
            return BaseResult.<String>error("评论内容不能为空").build();
        }
        if (StringUtils.isBlank(commentDto.getCommentatorName())) {
            return BaseResult.<String>error("称呼不能为空").build();
        }
        if (!StringUtils.isBlank(commentDto.getCommentatorEmail()) && !StringUtils.isEmailLegal(commentDto.getCommentatorEmail())) {
            return BaseResult.<String>error("邮箱格式不正确").build();
        }
        if (StringUtils.isBlank(commentDto.getCommentatorAvatar())) {
            commentDto.setCommentatorAvatar(null);
        }
        // 设置评论类型
        commentDto.setType(!StringUtils.isBlank(commentDto.getArticleId()) ? CommentTypeEnum.COMMENT.getValue() : CommentTypeEnum.MESSAGE.getValue());
        return BaseResult.<String>success().build();
    }
}
